////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file   CollabRCBotUnitTests\TestOperationTreeNode.h
///
/// @brief  Defines the test suite for CollabRC::Bot::AI::OperationTreeNode
////////////////////////////////////////////////////////////////////////////////////////////////////

#define _INCLUDED_TEST_OPERATION_TREE_NODE_H

#include <cxxtest/TestSuite.h>
#include "RandomMock.h"

#include <vector>
#include "../CollabRCBot/OperationTreeNode.h"
#include "../CollabRCBot/exceptions.h"
#include "../CollabRCBot/OperationTreeNodeImpl.h"
#include "../CollabRCBot/ComputationalTreeNodeVisitor.h"

using CollabRC::Bot::AI::ComputationalTreeNode;
using CollabRC::Bot::AI::OperationTreeNode;

class TestOperationTreeNode : public CxxTest::TestSuite
{
private:
    class UnclonableException : public std::exception
    {
    public:
        UnclonableException():exception("Clone not supported") {};
    };

    class MockOperationTreeNode : public CollabRC::Bot::AI::OperationTreeNode
    {
    private:
        mutable float m_lastLhs, m_lastRhs;
    public:
        inline virtual float PerformOperation(float lhs, float rhs) const
        {
            m_lastLhs = lhs;
            m_lastRhs = rhs;

            return lhs + rhs;
        };

        inline virtual ComputationalTreeNode* Clone() const {
            return new MockOperationTreeNode(*this);
        }

        inline float GetLastLhs() { return m_lastLhs; };
        inline float GetLastRhs() { return m_lastRhs; };
        inline const ComputationalTreeNode& GetLhs() { return *m_lhs; };
        inline const ComputationalTreeNode& GetRhs() { return *m_rhs; };

        MockOperationTreeNode(const ComputationalTreeNode &lhs, const ComputationalTreeNode &rhs):
            OperationTreeNode(lhs, rhs) { };

        MockOperationTreeNode(const MockOperationTreeNode& copy):
            OperationTreeNode(copy) { };
    };

    class MockValueNode : public CollabRC::Bot::AI::ComputationalTreeNode
    {
    private:
        const volatile float* m_value;
    public:
        inline virtual float Compute() const { return *m_value; };
        inline virtual unsigned int GetBranchDepth() const { return 1; };
        inline virtual const CollabRC::Bot::AI::ComputationalTreeNode& GetRandomBranch(unsigned int depth) const { return *this; };
        inline virtual ComputationalTreeNode* Clone() const { 
            return new MockValueNode(*this);
        };
        inline virtual ComputationalTreeNode* Mutate(unsigned int depth) const { 
            CollabRC::Bot::AI::IllegalMutationException ex; throw ex; 
        };

        inline void SetValue(const volatile float* v) { m_value = v; };
        inline const volatile float* GetValue() const { return m_value; };
        inline MockValueNode():m_value(NULL) { };
        inline MockValueNode(const volatile float* value):m_value(value) { };
        inline MockValueNode(const MockValueNode& copy):m_value(copy.m_value) { };
        inline void Accept(CollabRC::Bot::AI::ComputationalTreeNodeVisitor& visitor) {/* do nothing */ };
    };

    class MutableMockValueNode : public MockValueNode 
    {
    private:
        mutable bool m_mutated;

        MutableMockValueNode(const MutableMockValueNode& copy, bool mutated):
            m_mutated(mutated) { SetValue(copy.GetValue()); };
    public:
        inline virtual ComputationalTreeNode* Mutate(unsigned int depth) const 
        {
            TS_ASSERT_EQUALS(depth, 0);
            m_mutated = true;
            return new MutableMockValueNode(*this, true);
        }

        MutableMockValueNode():m_mutated(false) { };
        MutableMockValueNode(const MutableMockValueNode& copy):m_mutated(copy.m_mutated)
        {
            SetValue(copy.GetValue());
        }
        inline virtual ComputationalTreeNode* Clone() const { return new MutableMockValueNode(*this, m_mutated); };

        bool WasMutated() const { return m_mutated; };
        void ResetMutation() { m_mutated = false; };
        virtual void Accept(CollabRC::Bot::AI::ComputationalTreeNodeVisitor& visitor) { /* do nothing */ }
    };

    class UnclonableNode : public MutableMockValueNode
    {
    private:
        bool m_permitClone;
    public:
        inline UnclonableNode():m_permitClone(false) { };

        inline ComputationalTreeNode* Clone() const { 
            if (!m_permitClone)
            {
                UnclonableException ex; throw ex; 
            }
            else 
            {
                return new UnclonableNode;
            }
        }

        inline void PermitClone() { m_permitClone = true; };
    } unclonable;

    const float constValue;
    const float constValue2;
    MockValueNode constValueNode;
    MockValueNode constValueNode2;
    MockOperationTreeNode subnode;
    MockOperationTreeNode differingSubnode;
    MockOperationTreeNode lhsGreater;
    MockOperationTreeNode rhsGreater;
    MockOperationTreeNode deepNode;
    MutableMockValueNode mutableNode1, mutableNode2;
    MockOperationTreeNode mutableOperation;

    /// Ensures the static initializer for AdditionTreeNode is run
    CollabRC::Bot::AI::AdditionTreeNode dep;

public:
    TestOperationTreeNode():
      constValue(3), constValue2(4),
      constValueNode(&constValue), constValueNode2(&constValue2),
      subnode(constValueNode, constValueNode), differingSubnode(constValueNode, constValueNode2),
      lhsGreater(subnode, constValueNode),
      rhsGreater(constValueNode, subnode),
      deepNode(subnode, subnode),
      mutableNode1(),mutableNode2(),
      mutableOperation(mutableNode1, mutableNode2),
      dep(constValueNode, constValueNode2)
    {
        CollabRC::Bot::AI::AdditionTreeNode atn(constValueNode, constValueNode2);
    }

    void setUp()
    {
        constValueNode.SetValue(&constValue);
    }

    /**
     * Determines if n1 and n2 are the same MockValueNode.
     * Determines if both n1 and n2 are MockValueNodes and, if so,
     * they both point to the same value.
     * @param n1 the first node to test
     * @param n2 the second node to test
     * @return \c true if both n1 and n2 are MockValueNodes and both
     * point to the same value, or \c false otherwise
     */
    bool IsEquivalentValueNode(const ComputationalTreeNode& n1, const ComputationalTreeNode& n2)
    {
        const MockValueNode* m1 = dynamic_cast<const MockValueNode*>(&n1);
        const MockValueNode* m2 = dynamic_cast<const MockValueNode*>(&n2);
        return (m1 && m2 && m1->GetValue() == m2->GetValue());
    }

    /** Tests the Compute() operation of an OperationTreeNode */
    void TestCompute()
    {
        // Give it two values and make sure we're doing the computation
        // in the correct order
        float lhsValue = 4;
        float rhsValue = 5;
        MockValueNode lhs;
        MockValueNode rhs;

        lhs.SetValue(&lhsValue);
        rhs.SetValue(&rhsValue);

        MockOperationTreeNode operation(lhs, rhs);
        TS_ASSERT_EQUALS(operation.Compute(), lhs.Compute() + rhs.Compute());
        TS_ASSERT_EQUALS(operation.GetLastLhs(), lhsValue);
        TS_ASSERT_EQUALS(operation.GetLastRhs(), rhsValue);

        // Try changing each one and see that it still works
        lhsValue = 3;
        TS_ASSERT_EQUALS(operation.Compute(), lhs.Compute() +rhs.Compute());
        TS_ASSERT_EQUALS(operation.GetLastLhs(), lhsValue);
        TS_ASSERT_EQUALS(operation.GetLastRhs(), rhsValue);
        
        rhsValue = 4;
        TS_ASSERT_EQUALS(operation.Compute(), lhs.Compute() +rhs.Compute());
        TS_ASSERT_EQUALS(operation.GetLastLhs(), lhsValue);
        TS_ASSERT_EQUALS(operation.GetLastRhs(), rhsValue);        
    }

    /** Tests the GetBranchDepth() function */
    void TestBranchDepth()
    {
        // Test lhs branch depth == rhs branch depth
        TS_ASSERT_EQUALS(subnode.GetBranchDepth(), 2);

        // test lhs branch depth is greater
        TS_ASSERT_EQUALS(lhsGreater.GetBranchDepth(), 3);

        // test rhs branch depth is greater
        TS_ASSERT_EQUALS(rhsGreater.GetBranchDepth(), 3);
    }

    /** Tests the GetRandomBranch() function */
    void TestGetRandomBranch()
    {
        // 0 should return itself
        TS_ASSERT_EQUALS(&(differingSubnode.GetRandomBranch(0)), &differingSubnode);

        // 1 should return either the LHS or the RHS depending on RNG
        bool gotLhs = false, gotRhs = false;
        MinRandom min;

        const ComputationalTreeNode &node1 = differingSubnode.GetRandomBranch(1);
        if (IsEquivalentValueNode(node1, differingSubnode.GetLhs()))
            gotLhs = true;
        else if (IsEquivalentValueNode(node1, differingSubnode.GetRhs()))
            gotRhs = true;
        else
            TS_FAIL("differing.GetRandomBranch(1) @ 0 returned an invalid branch");

        // Now try the other side
        MaxRandom max;
        const ComputationalTreeNode& node2 = differingSubnode.GetRandomBranch(1);
        if (IsEquivalentValueNode(node2, differingSubnode.GetLhs()))
            gotLhs = true;
        else if (IsEquivalentValueNode(node2, differingSubnode.GetRhs()))
            gotRhs = true;
        else
            TS_FAIL("differingSubnode.GetRandomBranh(1) @ RAND_MAX returned an invalid branch");

        // Verify we got both
        TS_ASSERT(gotLhs && gotRhs);

        // Verify we cascade appropriately
        // No matter which direction we go, we should be getting the subsubnode
        TS_ASSERT(IsEquivalentValueNode(deepNode.GetRandomBranch(2), constValueNode));
    }

    /** 
     * Tests the mutation operation. Because the mock class does not 
     * register itself with OperationTreeNode, after a mutation, it should
     * be impossible for a mutation to result in the same type at that level.
     */
    void TestMutate()
    {
        // Test mutation of this operation
        MinRandom min;
        ComputationalTreeNode* mutation = subnode.Mutate(0);
        TS_ASSERT(dynamic_cast<MockOperationTreeNode*>(mutation) == NULL);
        delete mutation;

        // Test mutation at one level below
        bool gotLhs = false, gotRhs = false;
        bool lhsIsMin = false;  // Helps with next test
        MockOperationTreeNode* mutated1 = dynamic_cast<MockOperationTreeNode*>(mutableOperation.Mutate(1));
        TS_ASSERT(mutated1 != NULL);

        if (dynamic_cast<const MutableMockValueNode&>(mutated1->GetLhs()).WasMutated())
            gotLhs = lhsIsMin = true;
        else if (dynamic_cast<const MutableMockValueNode&>(mutated1->GetRhs()).WasMutated())
            gotRhs = true;
        else
            TS_FAIL("No mutation occurred at level 1");
        
        delete mutated1;

        mutableNode1.ResetMutation();
        mutableNode2.ResetMutation();

        // Check the other side
        MaxRandom max;
        mutated1 = dynamic_cast<MockOperationTreeNode*>(mutableOperation.Mutate(1));
        if (dynamic_cast<const MutableMockValueNode&>(mutated1->GetLhs()).WasMutated())
            gotLhs = true;
        else if (dynamic_cast<const MutableMockValueNode&>(mutated1->GetRhs()).WasMutated())
            gotRhs = true;
        else
            TS_FAIL("No mutation occurred at level 1");

        TS_ASSERT(gotLhs && gotRhs);
        delete mutated1;

        mutableNode1.ResetMutation();
        mutableNode2.ResetMutation();

        // Check mutation failure fallbacks        
        MinRandom min2;
        MockOperationTreeNode semiimmutable(
            lhsIsMin ? static_cast<const ComputationalTreeNode&>(constValueNode) : mutableNode1, 
            lhsIsMin ? static_cast<const ComputationalTreeNode&>(mutableNode1) : constValueNode
        );
        MockOperationTreeNode* mutated2 = dynamic_cast<MockOperationTreeNode*>(semiimmutable.Mutate(1));
        const MutableMockValueNode& mutated3 = dynamic_cast<const MutableMockValueNode&>(lhsIsMin ? mutated2->GetRhs() : mutated2->GetLhs());
        TS_ASSERT(&mutated3 != NULL);
        TS_ASSERT(mutated3.WasMutated());
        delete mutated2;

        mutableNode1.ResetMutation();
        mutableNode2.ResetMutation();

        MaxRandom max2;
        MockOperationTreeNode semiimmutable2(
            lhsIsMin ? static_cast<const ComputationalTreeNode&>(mutableNode1) : constValueNode,
            lhsIsMin ? static_cast<const ComputationalTreeNode&>(constValueNode) : mutableNode1
        );
        MockOperationTreeNode* mutated4 = dynamic_cast<MockOperationTreeNode*>(semiimmutable2.Mutate(1));
        const MutableMockValueNode& mutated5 = dynamic_cast<const MutableMockValueNode&>(lhsIsMin ? mutated4->GetLhs() : mutated4->GetRhs());

        
        // Check mutation exception cascade
        MockOperationTreeNode immutable(constValueNode, constValueNode);
        TS_ASSERT_THROWS(immutable.Mutate(1), CollabRC::Bot::AI::IllegalMutationException);

        // Check semi-immutable failed clone
        mutableNode1.ResetMutation();
        unclonable.PermitClone();
        MockOperationTreeNode semiimmutableUnclonable(
            lhsIsMin ? static_cast<const ComputationalTreeNode&>(mutableNode1) : unclonable,
            lhsIsMin ? static_cast<const ComputationalTreeNode&>(unclonable) : mutableNode1
        );
        TS_ASSERT_THROWS(semiimmutableUnclonable.Mutate(1), const UnclonableException&);
    }

    void TestCross()
    {

    }
    
};